// Author: Michel Barbeau, February 2004
// Updated: September 12, 2008
//
// File: wlanrecv.cpp
//

#include <netinet/in.h>

#include <sys/socket.h>
#include <features.h>    /* for the glibc version number */
#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h>     /* the L2 protocols */
#else
#include <asm/types.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>   /* The L2 protocols */
#endif

#include <linux/if.h> // for "struct ifreq"
//#include <linux/wireless.h> // for ther wireless extensions

#include <errno.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

#include <pthread.h>
#include <sys/types.h>

#include "wlan.hpp"

#include <vector>

#include "cryptoUtil.h"
#include "ElGamal.hpp"
#include "Diffie.hpp"
#include "RSA_.hpp"

   # define BUFFSIZE 1500

using namespace std;

// Remove these later once they're implemented


vector<tableEntry> secureTable;


// nodes that have completed the algorithm

pthread_mutex_t tableLock;

unsigned int wantToPrintPackets = 0;

void updateTable( WLANAddr * );

// 
// WLANAddr
//

char * WLANAddr::wlan2asc()
{
   static char str[32];
    
   sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", 
      data[0],data[1],data[2],data[3],data[4],data[5]);

   return str;
}

void pushToSecureTable (tableEntry in) {
	secureTable.push_back(in);
}
int checkSecureTable (WLANAddr * addr) {
	for (int i = 0; i < secureTable.size(); i++) {
		if (secureTable.at(i).address.data[0] == addr->data[0] &&
  			secureTable.at(i).address.data[1] == addr->data[1] &&		
			secureTable.at(i).address.data[2] == addr->data[2] &&		
			secureTable.at(i).address.data[3] == addr->data[3] &&
			secureTable.at(i).address.data[4] == addr->data[4] &&
			secureTable.at(i).address.data[5] == addr->data[5])
				return i;
	} 
	return -1;
}


void popFromSecureTable (int id) {
		// Swap and pop to remove from secureTable stack
		tableEntry temp = {NULL,0,0,0};
		temp = secureTable.at(secureTable.size()-1);
		secureTable.at(secureTable.size()-1) = secureTable.at(id);
		secureTable.at(id) = temp;
		secureTable.pop_back();
}


static int hexdigit(char a)
{
    if (a >= '0' && a <= '9') return(a-'0');
    if (a >= 'a' && a <= 'f') return(a-'a'+10);
    if (a >= 'A' && a <= 'F') return(a-'A'+10);
    return -1;
}

static int sscanf6(char *str, char *format, int *a1, int *a2, int *a3, int *a4, int *a5, int *a6)
{
    int n;

    *a1 = *a2 = *a3 = *a4 = *a5 = *a6 = 0;
    while ((n=hexdigit(*str))>=0)
        (*a1 = 16*(*a1) + n, str++);
    if (*str++ != ':') return 1;
    while ((n=hexdigit(*str))>=0)
        (*a2 = 16*(*a2) + n, str++);
    if (*str++ != ':') return 2;
    while ((n=hexdigit(*str))>=0)
        (*a3 = 16*(*a3) + n, str++);
    if (*str++ != ':') return 3;
    while ((n=hexdigit(*str))>=0)
        (*a4 = 16*(*a4) + n, str++);
    if (*str++ != ':') return 4;
    while ((n=hexdigit(*str))>=0)
        (*a5 = 16*(*a5) + n, str++);
    if (*str++ != ':') return 5;
    while ((n=hexdigit(*str))>=0)
        (*a6 = 16*(*a6) + n, str++);

    return 6;
}

Outcome WLANAddr::str2wlan(char * s)
{
   int a[6], i;
     
   // parse the address
   if (sscanf6(s, (char*)"%x:%x:%x:%x:%x:%x", a, a+1, a+2, a+3, a+4, a+5) < 6) 
   {
      return NOK;
   }

   // make sure the value of every component does not exceed on byte
   for (i=0; i < 6; i++) 
   {
      if (a[i] > 0xff) return NOK;
   }

   // assign the result to the "data" data member
   for (i=0; i < 6; i++) 
   {
      data[i] = a[i];
   }

   return OK;
}

// Global variables
   
// label of device, e.g. "eth0"
char device[] = "ra0";
// interface configuration
Ifconfig ifconfig_receive;
Ifconfig ifconfig_send;
 // prototype header
WLANHeader hdr;

Outcome init_receive()
{
   // (1) create device level socket
   // - PF_PACKET : low level packet interface
   // - SOCK_RAW : raw packets including link level header
   // - ETH_P_ALL : all frames will be received
   if ((ifconfig_receive.sockid = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1)
   { 
      printf("cannot open socket: %s\n", strerror(errno));
      return NOK;
   }

   // (2) fetch the interface index
   struct ifreq ifr;
   strcpy(ifr.ifr_name, device);
   if (ioctl(ifconfig_receive.sockid, SIOGIFINDEX, &ifr) < 0)
   {
      printf("failed to fetch ifindex: %s\n", strerror(errno));
      return NOK;
   }
   ifconfig_receive.ifindex = ifr.ifr_ifindex;
   printf("ifindex: %d\n", ifconfig_receive.ifindex);

   // (3) fetch the hardware address
   if (ioctl(ifconfig_receive.sockid, SIOCGIFHWADDR, &ifr) == -1)
   {
      printf("failed to fetch hardware address: %s\n", strerror(errno));
      return NOK;
   }
   memcpy(&ifconfig_receive.hwaddr.data, &ifr.ifr_hwaddr.sa_data, WLAN_ADDR_LEN);
   printf("hwaddr: %s\n", ifconfig_receive.hwaddr.wlan2asc());

   // (4) fetch the MTU
   if (ioctl(ifconfig_receive.sockid, SIOCGIFMTU, &ifr) == -1)
   {
      printf("WLANProtocol, failed to the MTU: %s\n", strerror(errno));
      return NOK;
   }
   ifconfig_receive.mtu = ifr.ifr_mtu;
   printf("MTU: %d\n", ifconfig_receive.mtu);

   // (5) add the promiscuous mode
   struct packet_mreq mr;
   memset(&mr,0,sizeof(mr));
   mr.mr_ifindex = ifconfig_receive.ifindex;
   mr.mr_type =  PACKET_MR_PROMISC;
   if (setsockopt(ifconfig_receive.sockid, SOL_PACKET, PACKET_ADD_MEMBERSHIP, 
      (char *)&mr, sizeof(mr)) < 0) 
   {
      printf("WLANProtocol, failed to add the promiscuous mode: %s\n", 
         strerror(errno)); 
      return NOK;
   }

   // (6) bind the socket to the interface (device)
   struct sockaddr_ll sll;
   memset(&sll, 0, sizeof(sll));
   sll.sll_family = AF_PACKET;
   sll.sll_ifindex = ifconfig_receive.ifindex;
   sll.sll_protocol = htons(ETH_P_ALL);
   if (bind(ifconfig_receive.sockid, (struct sockaddr*)&sll, sizeof(sll)) < 0) 
   {
      printf("WLANProtocol, failed to bind the socket: %s\n", strerror(errno)); 
      return NOK;
   }
   return OK;
}

Outcome init_send()
{
   // (1) create device level socket
   // - PF_PACKET : low level packet interface
   // - SOCK_RAW : raw packets including link level header
   // - ETH_P_ALL : all frames will be received
   if ((ifconfig_send.sockid = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1)
   { 
      printf("cannot open socket: %s\n", strerror(errno));
      return NOK;
   }

   // (2) fetch the interface index
   struct ifreq ifr;
   strcpy(ifr.ifr_name, device);
   if (ioctl(ifconfig_send.sockid, SIOGIFINDEX, &ifr) < 0)
   {
      printf("failed to fetch ifindex: %s\n", strerror(errno));
      return NOK;
   }
   ifconfig_send.ifindex = ifr.ifr_ifindex;
   printf("ifindex: %d\n", ifconfig_send.ifindex);

   // (3) fetch the hardware address
   if (ioctl(ifconfig_send.sockid, SIOCGIFHWADDR, &ifr) == -1)
   {
      printf("failed to fetch hardware address: %s\n", strerror(errno));
      return NOK;
   }
   memcpy(&ifconfig_send.hwaddr.data, &ifr.ifr_hwaddr.sa_data, WLAN_ADDR_LEN);
   printf("hwaddr: %s\n", ifconfig_send.hwaddr.wlan2asc());

   // (4) fetch the MTU
   if (ioctl(ifconfig_send.sockid, SIOCGIFMTU, &ifr) == -1)
   {
      printf("WLANProtocol, failed to the MTU: %s\n", strerror(errno));
      return NOK;
   }
   ifconfig_send.mtu = ifr.ifr_mtu;
   printf("MTU: %d\n", ifconfig_send.mtu);

   // (5) add the promiscuous mode
   struct packet_mreq mr;
   memset(&mr,0,sizeof(mr));
   mr.mr_ifindex = ifconfig_send.ifindex;
   mr.mr_type =  PACKET_MR_PROMISC;
   if (setsockopt(ifconfig_send.sockid, SOL_PACKET, PACKET_ADD_MEMBERSHIP, 
      (char *)&mr, sizeof(mr)) < 0) 
   {
      printf("WLANProtocol, failed to add the promiscuous mode: %s\n", 
         strerror(errno)); 
      return NOK;
   }

   // (6) bind the socket to the interface (device)
   struct sockaddr_ll sll;
   memset(&sll, 0, sizeof(sll));
   sll.sll_family = AF_PACKET;
   sll.sll_ifindex = ifconfig_send.ifindex;
   sll.sll_protocol = htons(ETH_P_ALL);
   if (bind(ifconfig_send.sockid, (struct sockaddr*)&sll, sizeof(sll)) < 0) 
   {
      printf("WLANProtocol, failed to bind the socket: %s\n", strerror(errno)); 
      return NOK;
   }
   return OK;
}

void printBuff( unsigned char * buff, unsigned int size )
{
	int i;
	for ( i = 0; i < size; i++ )
	{
		printf("%x", buff[i]);
		if ( i % 10 == 0 )
		{
			printf("\n");
		}
	}
}


/*
	Handle:
	- check if scrAddr is in local table
		- if TRUE
			- decrypt using public info in table

		- if FALSE
			IF message=RTS(random numbers, public key)
				- send your public info, establish keys

			ELSE RTS-info to srcAddr




*/

void* handleMessage( void * buff )
{
	CryptoPacket cp = {0};
   WLANHeader * pHdr;

   if ( buff != NULL )
	{
      pHdr = (WLANHeader *)buff;
      buff += sizeof(WLANHeader);
	
		// don't handle our own broadcast messages
		if ( compareMacAddrs( pHdr->srcAddr.data, ifconfig_send.hwaddr.data ) != 0 )
		{

			memcpy( &cp, buff, sizeof(CryptoPacket) );
			unsigned short algType = cp.id >> 4;

			//	unsigned short type = cp.id & 0x0F;


			//printf ("algType=%d\n",algType);
			switch (algType)
			{
			case RSA:
				printf("Handling RSA message\n");
				handleRSA( &cp, pHdr);
				break;
			case ELGAMAL: 
				printf("Handling ELGAMAL message\n");
				handleElGamal( &cp, pHdr); 
				break;
			case DIFFIEHELLMAN:

				printf("Handling DIFFIEHELLMAN message\n");
				handleDH( &cp, pHdr);

				break;
			}
		}
		else
		{
			//printf("Ignoring our own broadcast message\n");
		}
	}
	else
		printf ("invalid message (NULLs)");

   return NULL;
}

// receive data over a socket
void* Receive(void * ignore)
{
   // pointer to received data
   unsigned char * buff = new unsigned char[ifconfig_receive.mtu];
   ssize_t i; // frame length
   struct sockaddr_ll from; // source address of the  message
   socklen_t fromlen = sizeof(struct sockaddr_ll); 
   int error;
   void* (*pHandleMessage)(void*) = handleMessage;
   pthread_t * phandle_thread;

   // infinite loop
   while (true)
   {
      // (5) loop until a non-empty frame has been received on "device"
      while (true)
      {
         // (6) wait and receive a frame
         fromlen = sizeof(from);
         i = recvfrom(ifconfig_receive.sockid, buff, ifconfig_receive.mtu, 0, 
            (struct sockaddr *) &from, &fromlen);
         if (i == -1)
         {
            printf("cannot receive data: %s\n", strerror(errno));
            // sleep for 10 milliseconds before re-trying
            usleep(10000);
         }
	     else
	     {

            break; // exit the loop
	     }
      }
	
      //printf("frame received\n");

      char asciiSrcAddr[32], asciiDestAddr[32];
  
      // casting to the WLAN header format
      WLANHeader * wlanHdr = (WLANHeader *) buff;

      char * dest = wlanHdr->destAddr.wlan2asc();
      strcpy(asciiDestAddr, dest);
 
      char * src = wlanHdr->srcAddr.wlan2asc();
      strcpy(asciiSrcAddr, src);


	
     if ( wantToPrintPackets )
     {
	  printf("\nwlan:%s<<%s\n", asciiDestAddr, asciiSrcAddr);

      printf("msg=");
      // debug - what is the message anyways
      for (int q = WLAN_HEADER_LEN; q < i; q++)
			printf("%c",((char*)buff)[q]);
		  	printf("[EOM]\n");
     }

      phandle_thread = (pthread_t *)malloc(sizeof(pthread_t));
      if ( phandle_thread )
      {
         pthread_create (phandle_thread, NULL, pHandleMessage, buff);
      }

      printf("[end handle]\n");
   }

   return NULL;
}

void MySend(char * destAddressStr, char * message) 
{ 
   // send buffer size
   // send buffer
   unsigned char buff[BUFFSIZE]; 

   // destination address (binary)
   WLANAddr daddr;

   // prepare the prototype header

   // convert destination address from ASCII to binary
   daddr.str2wlan(destAddressStr);

   // store the destination address
   memmove(&hdr.destAddr, daddr.data, WLAN_ADDR_LEN);

   // store the source address
   memmove(&hdr.srcAddr, ifconfig_send.hwaddr.data, WLAN_ADDR_LEN);

   // set the type field
   hdr.type = htons(IP_TYPE);

   // store the header into the message 
   memmove(buff, &hdr, WLAN_HEADER_LEN);

   // store the payload
   memmove(buff+WLAN_HEADER_LEN, message, strlen(message));

   // set the "to address"
   struct sockaddr_ll to;
   int tolen = sizeof(to);
   to.sll_family = AF_PACKET;
   to.sll_ifindex = ifconfig_send.ifindex;
   memmove(&(to.sll_addr), daddr.data, WLAN_ADDR_LEN);
   to.sll_halen = 6;

   // (4) send a frame
   int sentlen;


	sentlen = sendto(ifconfig_send.sockid, buff, WLAN_HEADER_LEN+strlen(message), 0, (sockaddr *) &to, tolen);
	if (sentlen == -1 ) 
	{
	 printf("sendto() failed %s\n", strerror(errno));
	 //return NOK;
	}
	else
	{
	 printf("sendto() succeeded\n");
	}

}

void MySendData(unsigned char * destAddr, void * data) 
{ 
   // send buffer size

   // send buffer
   unsigned char buff[BUFFSIZE]; 
   int i;
   int dataSize = sizeof(CryptoPacket);
   PCryptoPacket pcp;

   // destination address (binary)
   WLANAddr daddr;

   // prepare the prototype header
   for (i=0; i<WLAN_ADDR_LEN; i++)
   {
	   daddr.data[i] = destAddr[i];
   }

   // store the destination address
   memmove(&hdr.destAddr, daddr.data, WLAN_ADDR_LEN);

   // store the source address
   memmove(&hdr.srcAddr, ifconfig_send.hwaddr.data, WLAN_ADDR_LEN);

   // set the type field
   hdr.type = htons(IP_TYPE);

   // store the header into the message 
   memmove(buff, &hdr, WLAN_HEADER_LEN);

   // store the payload
   memmove(buff+WLAN_HEADER_LEN, data, dataSize);

   // set the "to address"
   struct sockaddr_ll to;
   int tolen = sizeof(to);
   to.sll_family = AF_PACKET;
   to.sll_ifindex = ifconfig_send.ifindex;
   memmove(&(to.sll_addr), daddr.data, WLAN_ADDR_LEN);
   to.sll_halen = 6;

   // (4) send a frame
   int sentlen;

   printf("Sending from %s to %s\n", hdr.srcAddr.wlan2asc(), hdr.destAddr.wlan2asc());
	sentlen = sendto(ifconfig_send.sockid, buff, WLAN_HEADER_LEN+dataSize, 0, (sockaddr *) &to, tolen);
	if (sentlen == -1 ) 
	{
	 printf("mysendto() failed %s\n", strerror(errno));
	 //return NOK;
	}
	else
	{
	 printf("mysendto() succeeded\n");
	}

   if ( data != NULL )
   {
      free( data );
   }
}

void shutdown()
{
   printf("shutdown\n");
   // close the sockets
   if (ifconfig_receive.sockid != -1) close(ifconfig_receive.sockid);
   if (ifconfig_send.sockid != -1) close(ifconfig_send.sockid);
}

void updateTable( WLANAddr * addr )
{

   pthread_mutex_lock(&tableLock);


	bool isNeighbour = false;
	for (int i = 0; i < secureTable.size(); i++) {
		// compare the 6 data values
		if (secureTable.at(i).address.data[0] == addr->data[0] &&
  			secureTable.at(i).address.data[1] == addr->data[1] &&		
			secureTable.at(i).address.data[2] == addr->data[2] &&		
			secureTable.at(i).address.data[3] == addr->data[3] &&
			secureTable.at(i).address.data[4] == addr->data[4] &&
			secureTable.at(i).address.data[5] == addr->data[5]) {

				isNeighbour = true;
				printf("Hello\n");
				break;
		}
	}


   if ( addr == NULL )
      printf("Aging\n");

// Fix this later to print properly
	for (int i = 0; i < secureTable.size(); i++)
		printf("%s\n",secureTable.at(i).address.wlan2asc());	

   pthread_mutex_unlock(&tableLock);
}

void printSecurePeerTable(unsigned int type){

	printf ("WLANAddr\t\tKey\t\tTime\n");
	printf("----------\t\t-----\t\t------\n");
		
	for (int i = 0; i < secureTable.size(); i++) {
		//if (secureTable.at(i).type == type) {
			char asciiAddr[32];
			char * dest = secureTable.at(i).address.wlan2asc();
			strcpy(asciiAddr, dest);
			printf ("%s\t%u\t%f\n",asciiAddr, secureTable.at(i).result, secureTable.at(i).time);
		//}
	}	


}


main()
{
   pthread_t receive_thread;
   void* (*pReceive)(void*) = Receive;

   int ret, input; 
   int done = 0;

   /* initialize a mutex to its default value */ 
   ret = pthread_mutex_init(&tableLock, NULL);


   if (init_receive()==OK &&
       init_send()==OK) 
   {
      pthread_create (&receive_thread, NULL, pReceive, (void*)NULL);

	  while (done == 0)
	  {
		  printf("\nMenu:\n");
		  printf("1. Initiate RSA\n");
		  printf("2. Initiate ElGamal\n");
		  printf("3. Initiate Diffie Hellman\n");
		  printf("4. Print ElGamal peer table\n");
		  printf("5. Print DiffieHellman peer table\n");
		  printf("6. Print RSA peer table\n");
		  printf("0. Quit\n");
		  printf("> ");
		  fflush(stdin);
		  scanf("%d", &input);
		  printf("\n");
		  switch ( input ) 
		  {
		  case 1:

   			  initiateRSA();  
               sleep(1);
        
			  break;
		  case 2:

			      initiateElGamal();
               sleep(1);
    
			  break;
		  case 3:
 
   			  initiateDiffie();
               sleep(1);
       
			  break;
		  case 4:
			  printElGamalPeerTable();
			  break;
		  case 5:
			  printSecurePeerTable(DIFFIEHELLMAN);
			  break;
		  case 6:
			  printRSATable();
			  break;
		  case 0:
			  done = 1;
			  break;
		  default:
			  printf("Invalid input.\n\n");
		  }
	  }
   }

   pthread_cancel(receive_thread);
   shutdown();
   ret = pthread_mutex_destroy(&tableLock);
}
